package org.hismig.scheduler;

import java.text.ParseException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import org.hismig.cfg.Configuration;
import org.hismig.cfg.Trigger;
import org.hismig.migration.Constructor;
import org.hismig.migration.Context;
import org.hismig.migration.executor.DBExecutor;
import org.hismig.migration.result.JobResultInfo;
import org.hismig.utils.CheckTool;
import org.quartz.CronExpression;
import org.quartz.CronScheduleBuilder;
import org.quartz.Job;
import org.quartz.JobBuilder;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.TriggerBuilder;
import org.quartz.impl.StdSchedulerFactory;

/**
 * @declare: //TODO <br />
 *           This class created by Vansion.Hopihit on 2012-07-04 <br />
 *           <a href="mailto:vansionhopihit@gmail.com">Contact Me</a>
 * @version 1.0
 * @author Vansion.Hopihit
 * 
 */
public class QuartzTrigger implements ITrigger, Job {

	private Scheduler scheduler;

	private org.quartz.Trigger trigger;

	private Trigger trigger_cfg;

	private static List<ITriggerListener> triggerListeners = new ArrayList<ITriggerListener>();

	private static final String KEY_CONFIGURATION = "configuration";

	private static Date previousFireTime; // to provide the previousFireTime

	private CronExpression cron; // to resolve getNextFireTime

	@Override
	public void setTrigger(Trigger trigger) {
		CheckTool.checkNotNull(trigger);

		trigger_cfg = trigger;
	}

	@Override
	public void execute(JobExecutionContext jobExecutionContext)
			throws JobExecutionException {

		Configuration configuration = (Configuration) jobExecutionContext
				.getJobDetail().getJobDataMap().get(KEY_CONFIGURATION);

		Date startDate = new Date();
		previousFireTime = new Date();

		JobResultInfo jobResultInfo = new JobResultInfo(startDate.toString());
		jobResultInfo.setStartDate(startDate);
		jobResultInfo.setStartTime(System.currentTimeMillis());

		Constructor constructor = new Constructor();
		constructor.init(configuration);

		Context context = constructor.initContext(null);

		List<DBExecutor> dbExecutors = constructor.getDbExecutors();

		try {
			for (ITriggerListener triggerListener : triggerListeners) {
				triggerListener.beforeTriggerStart(context, dbExecutors);
			}
		} catch (Exception e) {
			jobResultInfo.addException(e);
		}

		for (DBExecutor dbExecutor : dbExecutors) {
			jobResultInfo.addDbResultInfo(dbExecutor.execute(context));
		}

		jobResultInfo.setEndTime(System.currentTimeMillis());
		jobResultInfo.setEndDate(new Date());

		try {
			for (ITriggerListener triggerListener : triggerListeners) {
				triggerListener.afterTriggerEnd(jobResultInfo);
			}
		} catch (Exception e) {
			jobResultInfo.addException(e);
		}
	}

	@Override
	public void execute(Configuration configuration) {
		CheckTool.checkNotNull(configuration);

		// first, shutdown the old scheduler
		shutdown(false);

		scheduler = getScheduler(configuration);

		try {
			scheduler.start();
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	public void shutdown(boolean immediately) {
		if (null != scheduler) {
			try {
				if (!scheduler.isShutdown()) {
					scheduler.shutdown(immediately);
				}
			} catch (SchedulerException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}

	public void pause() {

		if (null != scheduler) {
			try {
				scheduler.pauseAll();
			} catch (SchedulerException e) {
				e.printStackTrace();
			}
		}

	}

	private Scheduler getScheduler(Configuration configuration) {

		String expression = configuration.getTrigger().getParamByName(
				KEY_EXPRESSION);

		CheckTool.checkNotEmpty(expression);

		Scheduler scheduler = null;

		try {
			cron = new CronExpression(expression);
		} catch (ParseException e) {
			throw new IllegalArgumentException(
					String.format(
							"Parse the CronExpression fail.[expression=%s]",
							expression), e);
		}

		JobDetail job = JobBuilder.newJob(QuartzTrigger.class)
				.withIdentity("hismig-job", "default").build();

		job.getJobDataMap().put(KEY_CONFIGURATION, configuration);

		trigger = TriggerBuilder.newTrigger().forJob(job)
				.withIdentity("hismig-trigger", "default")
				.withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();

		SchedulerFactory sf = new StdSchedulerFactory();

		try {
			scheduler = sf.getScheduler();
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		try {
			scheduler.scheduleJob(job, trigger);
		} catch (SchedulerException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}

		return scheduler;
	}

	public void addListener(ITriggerListener triggerListener) {
		if (!triggerListeners.contains(triggerListener)) {
			triggerListeners.add(triggerListener);
		}
	}

	public void removeListener(ITriggerListener triggerListener) {
		if (triggerListeners.contains(triggerListener)) {
			triggerListeners.remove(triggerListener);
		}
	}

	@Override
	public Date getNextFireTime() {
		// if (null != trigger) {
		// return trigger.getNextFireTime();
		// }

		return cron.getNextValidTimeAfter(new Date());
	}

	@Override
	public Date getPreviousFireTime() {
		// if (null != trigger) {
		// return trigger.getPreviousFireTime();
		// }
		return previousFireTime;
	}

	@Override
	public String getName() {
		return QuartzTrigger.class.getSimpleName();
	}

	@Override
	public Trigger getTrigger() {
		return trigger_cfg;
	}

	@Override
	public void resume() {
		try {
			if (null != scheduler) {
				scheduler.resumeAll();
			}
		} catch (SchedulerException e) {
			e.printStackTrace();
		}

	}
}
